﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Xml;
using System.Xml.XPath;

namespace XML2Constellation
{
	enum XMLElement
	{
		ConstellationList,
		ConstellationSegment,
		Star,
		Constellation,
		Name,
	}

	enum XMLAttribute
	{
		RightAscension,
		Declination,
		Abbreviation,
		Case,
	}

	internal static class ReaderExtensions
	{
		const string NS = "urn:denebsoft-skymap-constellation";

		internal static XPathNodeIterator SelectChildren(this XPathNavigator reader, XMLElement name)
		{
			return reader.SelectChildren(name.ToString(), NS);
		}

		internal static XPathNodeIterator SelectDescendants(this XPathNavigator reader, XMLElement name, bool matchSelf = false)
		{
			return reader.SelectDescendants(name.ToString(), NS, matchSelf);
		}

		internal static void ForEachChildren(this XPathNavigator reader, XMLElement name, Action<XPathNavigator> body)
		{
			var childs = reader.SelectChildren(name.ToString(), NS);
			foreach (XPathNavigator child in childs)
				body(child);
		}

		internal static void ForEachChildren(this XPathNavigator reader, XMLElement name, Action<int> count, Action<XPathNavigator> body)
		{
			var childs = reader.SelectChildren(name.ToString(), NS);
			count(childs.Count);
			foreach (XPathNavigator child in childs)
				body(child);
		}

		internal static double GetAttributeDouble(this XPathNavigator reader, XMLAttribute name)
		{
			string value = reader.GetAttribute(name.ToString(), String.Empty);
			if (String.IsNullOrEmpty(value))
				throw new ArgumentException(name.ToString());
			return XmlConvert.ToDouble(value);
		}

		internal static string GetAttribute(this XPathNavigator reader, XMLAttribute name)
		{
			return reader.GetAttribute(name.ToString(), String.Empty);
		}

		internal static string GetAttributeLanguage(this XPathNavigator reader)
		{
			return reader.XmlLang;
		}
	}

	internal static class WriterExtensions
	{
		internal static void Write(this BinaryWriter writer, double? value)
		{
			if (!value.HasValue)
				throw new ArgumentNullException("value");
			writer.Write(value.Value);
		}

		internal static void Write(this BinaryWriter writer, IEnumerable<string> value)
		{
			string str = String.Join(", ", value);
			writer.Write(String.IsNullOrEmpty(str) ? " " : str);
		}
	}

	class Program
	{
		static void Main(string[] args)
		{
			string ifn = args.Length == 0 || Path.GetExtension(args[0]) == ".dat" ? "constellation.xml" : args[0];
			string ofn = args.Length == 1 && Path.GetExtension(args[0]) == ".dat" ? args[0] : args.Length <= 1 ? "constellation.dat" : args[1];

			if (args.Length > 2 || Path.GetExtension(ifn) != ".xml" || Path.GetExtension(ofn) != ".dat" ||
				args.Length > 1 && Path.GetExtension(args[0]) == ".dat")
			{
				Console.WriteLine("usage: {0} [constellation.xml] [constellation.dat]", Path.GetFileNameWithoutExtension(System.Reflection.Assembly.GetExecutingAssembly().Location));
				return;
			}

			var doc = new XPathDocument(ifn);
			var reader = doc.CreateNavigator();
			using (var ofs = new FileStream(ofn, FileMode.Create))
			using (var writer = new BinaryWriter(ofs))
			{
				reader.MoveToFirstChild(); //ConstellationList

				writer.Write(reader.SelectDescendants(XMLElement.Star).Count -
					reader.SelectChildren(XMLElement.ConstellationSegment).Count);
				reader.ForEachChildren(XMLElement.ConstellationSegment, seg =>
				{
					double? LastRA = null;
					double? LastDE = null;
					seg.ForEachChildren(XMLElement.Star, star =>
					{
						var RA = star.GetAttributeDouble(XMLAttribute.RightAscension);
						var DE = star.GetAttributeDouble(XMLAttribute.Declination);
						if (LastRA.HasValue)
						{
							writer.Write(LastRA);
							writer.Write(RA);
							writer.Write(LastDE);
							writer.Write(DE);
						}
						LastRA = RA; LastDE = DE;
					});
				});

				bool other = false;
				reader.ForEachChildren(XMLElement.Constellation, count => writer.Write(count), con =>
				{
					var RA = con.GetAttributeDouble(XMLAttribute.RightAscension);
					var Dec = con.GetAttributeDouble(XMLAttribute.Declination);
					string ShortName = con.GetAttribute(XMLAttribute.Abbreviation);

					string FullName = null;
					con.ForEachChildren(XMLElement.Name, name =>
					{
						if (name.GetAttributeLanguage() == "la" && String.IsNullOrEmpty(name.GetAttribute(XMLAttribute.Case)))
							FullName = name.Value;
						else if (!other && name.GetAttributeLanguage() != "la")
							other = true;

					});

					writer.Write(RA);
					writer.Write(Dec);
					writer.Write(ShortName);
					writer.Write(FullName ?? " ");
				});

				if (other)
					reader.ForEachChildren(XMLElement.Constellation, count => writer.Write(count), con =>
					{
						var FullName = new List<string>();
						string GenName = null;
						con.ForEachChildren(XMLElement.Name, name =>
						{
							if (name.GetAttributeLanguage() == "ru")
								if (String.IsNullOrEmpty(name.GetAttribute(XMLAttribute.Case)))
									FullName.Add(name.Value);
								else if (name.GetAttribute(XMLAttribute.Case) == "Genitive")
									GenName = name.Value;
						});

						writer.Write(FullName);
						writer.Write(GenName ?? " ");
					});
			}
		}
	}
}
